audio logging and diagnostics with structlog - #47
Conversation
- Add GAMBATERM_AUDIO_LOG and GAMBATERM_AUDIO_CSV to report statistics, replacing some 'TODO' items about logging under and overruns. - Adds common 'fill_fraction' property, to determine - Changed to a batch variable-length emulator audio in send(). runFor() may return only a few hundred samples at video-frame boundaries; feeding those directly to the resampler can starve the ring buffer, instead, accumulate until the batch is large enough prevents this. In practicality, an underrun only occurs when the system is under load, I have only induced a starved ring buffer by using too much cpu by encoding kitty graphics in render(). I plan to address this by "banding", but I think this fix can be helpful for lower end computers or when the system is under load?
Prevent fill_fraction from returning a negative value if read_counter ever exceeds write_counter (should never happen, but this is a cheap safety net).
| # Accumulate input to batch variable-length emulator frames into consistent chunks for the | ||
| # resampler. The emulator's runFor() may produce anywhere from a few hundred to tens of | ||
| # thousands of samples per call; tiny batches could otherwise starve the ring buffer when | ||
| # under load. |
There was a problem hiding this comment.
When I introduced the circular buffer, I had in mind that variable-length audio batches would not be a problem since I made sure that the core loop itself is synchronized with the number of produced audio samples:
gambatte-terminal/gambaterm/run.py
Lines 201 to 209 in f6fbf74
So in theory, the producer (i.e gambatte) and the consumer (miniaudio) should move at the exact same rate. The reason why they drift appart is because the producer uses the cpu clock, and the consumer the audio clock. So the job of the PI controller is to detect this slow drift and tune the resample rate to compensate for it. Since the drift is slow, monitoring a rolling average of the fill ratio is a good way to detect and adapt to the drift.
So as far as I can tell, tiny audio batches should not starve the ring buffer since they should be produced faster to compensate (unless the producing of the video frames at a faster rates slows down the core loop, although this should already be accounted for)
Do you think my analysis is correct? Did I miss something?
There was a problem hiding this comment.
I can only induce a starved audio buffer by spending too much CPU in rendering, the text mode blitter doesn't consume enough CPU to show the effect. In any case, I will change the render logic there instead, from "always render at least one partial graphics frame", to "skip rendering this frame all together when fill_fraction is low, to allow subsequent frames to fill it back up again before I take too much CPU time rendering it"
There was a problem hiding this comment.
I will change the render logic there instead, from "always render at least one partial graphics frame", to "skip rendering this frame all together when fill_fraction is low
I already implemented some logic to detect when the main loop can't keep up with the clock, and skip rendering+outputting the video frame if that's the case:
gambatte-terminal/gambaterm/run.py
Lines 207 to 209 in f6fbf74
gambatte-terminal/gambaterm/run.py
Lines 142 to 150 in f6fbf74
It's too bad that it's not enough to keep the audio circular buffer half-full. There are other solutions we can try in order to fix this:
- more agressive shifting detection:
shifting[-1] > 1 / fps / 2(or more) - increase the
audio_delay_in_framesto 4 or 5, this should give the buffer more time to adapt to sporadic heavy video frames
| logging.basicConfig(**_cfg) | ||
| logging.getLogger().setLevel(lvl) | ||
| logging.getLogger(name).setLevel(lvl) | ||
| return logging.getLogger(name) |
There was a problem hiding this comment.
Oh right, I will wire it there, I will have to look at structlog to see what feature is used or useful instead of standard logging
Can't we simply force a gambatte-terminal/gambaterm/ssh.py Lines 415 to 417 in f6fbf74 |
Replace the TODO stubs for underrun/overrun logging and add per-frame audio telemetry via
structlog.Add
--logfile/--loglevel/--logfmton all entry points (local, SSH, telnet), this is copied from telnetlib3. This is tricky because we really shouldn't log anything when run locally unless--logfileis also used .. so I have changed loglevel to default to critical for local execution, and info for ssh/telnet.